/*
 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.nio.file;

import java.nio.file.attribute.*;
import java.io.InputStream;
import java.io.IOException;

/**
 * Helper class to support copying or moving files when the source and target
 * are associated with different providers.
 */

class CopyMoveHelper {

  private CopyMoveHelper() {
  }

  /**
   * Parses the arguments for a file copy operation.
   */
  private static class CopyOptions {

    boolean replaceExisting = false;
    boolean copyAttributes = false;
    boolean followLinks = true;

    private CopyOptions() {
    }

    static CopyOptions parse(CopyOption... options) {
      CopyOptions result = new CopyOptions();
      for (CopyOption option : options) {
        if (option == StandardCopyOption.REPLACE_EXISTING) {
          result.replaceExisting = true;
          continue;
        }
        if (option == LinkOption.NOFOLLOW_LINKS) {
          result.followLinks = false;
          continue;
        }
        if (option == StandardCopyOption.COPY_ATTRIBUTES) {
          result.copyAttributes = true;
          continue;
        }
        if (option == null) {
          throw new NullPointerException();
        }
        throw new UnsupportedOperationException("'" + option +
            "' is not a recognized copy option");
      }
      return result;
    }
  }

  /**
   * Converts the given array of options for moving a file to options suitable
   * for copying the file when a move is implemented as copy + delete.
   */
  private static CopyOption[] convertMoveToCopyOptions(CopyOption... options)
      throws AtomicMoveNotSupportedException {
    int len = options.length;
    CopyOption[] newOptions = new CopyOption[len + 2];
    for (int i = 0; i < len; i++) {
      CopyOption option = options[i];
      if (option == StandardCopyOption.ATOMIC_MOVE) {
        throw new AtomicMoveNotSupportedException(null, null,
            "Atomic move between providers is not supported");
      }
      newOptions[i] = option;
    }
    newOptions[len] = LinkOption.NOFOLLOW_LINKS;
    newOptions[len + 1] = StandardCopyOption.COPY_ATTRIBUTES;
    return newOptions;
  }

  /**
   * Simple copy for use when source and target are associated with different
   * providers
   */
  static void copyToForeignTarget(Path source, Path target,
      CopyOption... options)
      throws IOException {
    CopyOptions opts = CopyOptions.parse(options);
    LinkOption[] linkOptions = (opts.followLinks) ? new LinkOption[0] :
        new LinkOption[]{LinkOption.NOFOLLOW_LINKS};

    // attributes of source file
    BasicFileAttributes attrs = Files.readAttributes(source,
        BasicFileAttributes.class,
        linkOptions);
    if (attrs.isSymbolicLink()) {
      throw new IOException("Copying of symbolic links not supported");
    }

    // delete target if it exists and REPLACE_EXISTING is specified
    if (opts.replaceExisting) {
      Files.deleteIfExists(target);
    } else if (Files.exists(target)) {
      throw new FileAlreadyExistsException(target.toString());
    }

    // create directory or copy file
    if (attrs.isDirectory()) {
      Files.createDirectory(target);
    } else {
      try (InputStream in = Files.newInputStream(source)) {
        Files.copy(in, target);
      }
    }

    // copy basic attributes to target
    if (opts.copyAttributes) {
      BasicFileAttributeView view =
          Files.getFileAttributeView(target, BasicFileAttributeView.class);
      try {
        view.setTimes(attrs.lastModifiedTime(),
            attrs.lastAccessTime(),
            attrs.creationTime());
      } catch (Throwable x) {
        // rollback
        try {
          Files.delete(target);
        } catch (Throwable suppressed) {
          x.addSuppressed(suppressed);
        }
        throw x;
      }
    }
  }

  /**
   * Simple move implements as copy+delete for use when source and target are
   * associated with different providers
   */
  static void moveToForeignTarget(Path source, Path target,
      CopyOption... options) throws IOException {
    copyToForeignTarget(source, target, convertMoveToCopyOptions(options));
    Files.delete(source);
  }
}
